Analýza volatilních pohybů v Pythonu a Pandas 1

V následujícím grafu jsou pro příklad zvýrazněny volatilní pohyby:

Volatilní pohyb je jedna z možností, jak sledovat a analyzovat tržní pohyby, které jsou založeny na emocích. Pokud se cena určité komodity rychle mění, vyvolává to v obchodnících silné emoce. Pokud např. cena nemovitostí během roku vzroste o 100% a má tendenci dále růst, všimnou si toho všichni a ti, co by s koupí nemovitosti otálely, si velmi rychle uspíší ji koupit za aktuální cenu, protože když budou čekat, mohla by cena být někde opravdu vysoko a oni by si už nemohli dovolit nemovitost koupit. A čím rychleji cena stoupá, tím více lidí se nákup snaží uspíšit. Otázka zní:

"Je dobré nakupovat s nimi, nebo je lepší jim danou věc prodat"?

Jak lze identifikovat volatilní pohyb pomocí Pythonu a Pandas?

Budu vycházet ze zdarma dostupných EOD (End Of Day) dat trhu SPY (ETF, kopírující hlavní americký akciový index S&P 500), který můžu stáhnout z yahoo finance pomocí pandas-datareader. Online graf je možné vidět na https://finance.yahoo.com/chart/SPY.


In [1]:
import pandas as pd
import pandas_datareader.data as web
import datetime

start = datetime.datetime(2015, 1, 1)
end = datetime.datetime(2018, 8, 31)

spy_data = web.DataReader('SPY', 'yahoo', start, end)
spy_data = spy_data.drop(['Volume', 'Adj Close'], axis=1) # sloupce 'Volume' a 'Adj Close' nebudu potřebovat
spy_data.tail()


Out[1]:
High Low Open Close
Date
2018-08-27 289.899994 288.679993 288.859985 289.779999
2018-08-28 290.420013 289.399994 290.299988 289.920013
2018-08-29 291.739990 289.890015 290.160004 291.480011
2018-08-30 291.359985 289.630005 290.940002 290.299988
2018-08-31 290.809998 289.290009 289.839996 290.309998

Každý řádek představuje cenu pro daný den a to nejvyšší (High), nejnižší (Low), otevírací (Open - začátek dne) a uzavírací (Close - konec dne). Volatilní pohyb pro daný den pak vidím v grafu na první pohled jako výrazné velké svíce (viz. graf 1). Abych je mohl automaticky v mém analytickém softwaru (python - pandas) označit, definuji volatilní svíce pomocí pravidla například jako:

Velikost změny ceny musí být větší než 4 předchozí svíce

K tomuto zjištění musím vypočítat velikost vzdálenosti pro jednotlivé svíce $Close-Open$. Pandas mi tuhle práci velmi pěkně usnadní:


In [2]:
spy_data['C-O'] = spy_data['Close'] - spy_data['Open']
spy_data.tail()


Out[2]:
High Low Open Close C-O
Date
2018-08-27 289.899994 288.679993 288.859985 289.779999 0.920013
2018-08-28 290.420013 289.399994 290.299988 289.920013 -0.379974
2018-08-29 291.739990 289.890015 290.160004 291.480011 1.320007
2018-08-30 291.359985 289.630005 290.940002 290.299988 -0.640015
2018-08-31 290.809998 289.290009 289.839996 290.309998 0.470001

Nyní znám přesnou změnu ceny každý den. Abych mohl porovnávat velikosti, aniž by mi záleželo na tom, zda se daný den cena propadla, nebo stoupla, aplikuji absolutní hodnotu.


In [3]:
spy_data['Abs(C-O)'] = spy_data['C-O'].abs()
spy_data.tail()


Out[3]:
High Low Open Close C-O Abs(C-O)
Date
2018-08-27 289.899994 288.679993 288.859985 289.779999 0.920013 0.920013
2018-08-28 290.420013 289.399994 290.299988 289.920013 -0.379974 0.379974
2018-08-29 291.739990 289.890015 290.160004 291.480011 1.320007 1.320007
2018-08-30 291.359985 289.630005 290.940002 290.299988 -0.640015 0.640015
2018-08-31 290.809998 289.290009 289.839996 290.309998 0.470001 0.470001

Identifikace volatilní úsečky

Volatilní svíce identifikuji pomocí funkcionality rolling a funkce apply. Funkcionalita rolling umožnuje rozdělit pandas DataFrame na jednotlivé menší "okna", které bude předávat postupně funkci apply v parametru. Tzn. že v následujícím kódu se provede výpočet funkce is_bigger pro každý řádek dat uložených v spy_data. Do parametru rows se bude postupně vkládat výřez dat, obsahující 4 řádky (aktuální počátaný řádek + 3 řádky předchozí). Jako výsledek funkce is_bigger bude hodnota, zda je aktuálně počítaný řádek volatilnější, než 4 předchozí.


In [4]:
def is_bigger(rows):
    result = rows[-1] > rows[:-1].max() # rows[-1] - poslední hodnota je větší než maximum z předchozích
    return result

spy_data['VolBar'] = spy_data['Abs(C-O)'].rolling(4).apply(is_bigger,raw=True)
spy_data.tail(10)


Out[4]:
High Low Open Close C-O Abs(C-O) VolBar
Date
2018-08-20 285.970001 285.059998 285.570007 285.670013 0.100006 0.100006 0.0
2018-08-21 287.309998 285.709991 286.250000 286.339996 0.089996 0.089996 0.0
2018-08-22 286.760010 285.579987 285.880005 286.170013 0.290009 0.290009 0.0
2018-08-23 286.940002 285.429993 285.970001 285.790009 -0.179993 0.179993 0.0
2018-08-24 287.670013 286.380005 286.440002 287.510010 1.070007 1.070007 1.0
2018-08-27 289.899994 288.679993 288.859985 289.779999 0.920013 0.920013 0.0
2018-08-28 290.420013 289.399994 290.299988 289.920013 -0.379974 0.379974 0.0
2018-08-29 291.739990 289.890015 290.160004 291.480011 1.320007 1.320007 1.0
2018-08-30 291.359985 289.630005 290.940002 290.299988 -0.640015 0.640015 0.0
2018-08-31 290.809998 289.290009 289.839996 290.309998 0.470001 0.470001 0.0

Které svíce jsou volatilnější, než 4 předchozí, si zobrazím pomocí jednoduché selekce, kde ve sloupečku VolBar == 1.


In [5]:
spy_data[spy_data['VolBar'] == 1].tail()


Out[5]:
High Low Open Close C-O Abs(C-O) VolBar
Date
2018-08-02 282.579987 279.160004 279.390015 282.390015 3.000000 3.000000 1.0
2018-08-13 284.160004 281.769989 283.470001 282.100006 -1.369995 1.369995 1.0
2018-08-17 285.559998 283.369995 283.829987 285.059998 1.230011 1.230011 1.0
2018-08-24 287.670013 286.380005 286.440002 287.510010 1.070007 1.070007 1.0
2018-08-29 291.739990 289.890015 290.160004 291.480011 1.320007 1.320007 1.0

Příště se zaměřím na to, jak jednoduše tyhle volatilní úsečky analyzovat.